package be.destin.rdf.changes;

import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;

import org.apache.log4j.Logger;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.impl.LiteralImpl;
import org.openrdf.model.impl.StatementImpl;
import org.openrdf.model.impl.URIImpl;

import be.destin.skos.core.Concept;
import be.destin.skos.core.ConceptScheme;
import be.destin.skos.core.LinkedConcept;
import be.destin.skos.core.LocalLinkedConcept;
import be.destin.skos.core.NoScheme;
import be.destin.skos.core.Notation;
import be.destin.skos.core.Predicate;
import be.destin.skos.core.SchemeInterface;
import be.destin.skos.core.SkosManager;
import be.destin.skos.core.SkosUtil;
import be.destin.skos.core.Term;
import be.destin.skos.core.TermFormat;
import be.destin.skos.core.TermList;
import be.destin.skos.core.Url;

/*
 * -- Table: rdfid

-- DROP TABLE rdfid;

CREATE TABLE rdfid
(
  subject_prefix integer NOT NULL,
  subject_about character varying(127) NOT NULL,
  predicate_prefix integer NOT NULL,
  predicate_about character varying(63) NOT NULL,
  object_prefix integer NOT NULL,
  object_about character varying(16000) NOT NULL,
  id integer NOT NULL DEFAULT nextval('seq_rdfid'::regclass),
  CONSTRAINT change_pkey PRIMARY KEY (id),
  CONSTRAINT rdfid_object_prefix_fkey FOREIGN KEY (object_prefix)
      REFERENCES prefix (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT rdfid_predicate_prefix_fkey FOREIGN KEY (predicate_prefix)
      REFERENCES prefix (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT rdfid_subject_prefix_fkey FOREIGN KEY (subject_prefix)
      REFERENCES prefix (id) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT rdfid_subject_prefix_key UNIQUE (subject_prefix, subject_about, predicate_prefix, predicate_about, object_prefix, object_about)
)
WITH (
  OIDS=FALSE
);
ALTER TABLE rdfid OWNER TO postgres;

-- Index: fki_

-- DROP INDEX fki_;

CREATE INDEX fki_
  ON rdfid
  USING btree
  (predicate_prefix);

-- Index: fki_object_prefix

-- DROP INDEX fki_object_prefix;

CREATE INDEX fki_object_prefix
  ON rdfid
  USING btree
  (object_prefix);


 */
public class SkosStatement extends StatementImpl {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	/** log4j category */
	private static Logger log = Logger.getLogger(SkosStatement.class);

	long id;
	String relationQualifier; /* Extends a RDF statement with a qualifier to the predicate */
	String termSource; /* Extends a RDF statement with a qualifier to the term */

	public static final Status[][] transitions = {
		/* no status */			{ Status.New_Proposed },
		/* New_Proposed */		{ Status.New_Under_Review, Status.Not_Approved }, // abandon by the proposer
		/* New_Under_Review */	{ Status.Published, Status.Not_Approved },
		/* Published */			{ Status.Deprecate_Proposed, Status.Change_Proposed }, 
		/* Change_Proposed */	{ Status.Change_Under_Review }, // abandon by the proposer or rejection by the manager
		/* Change_Under_Review*/{ Status.Published }, // or rejection by the reviewers
		/* Deprecate_Proposed */{ Status.Deprecate_Under_Review }, // abandon by the proposer
		/* Deprecate_Under_Review*/{ Status.Published, Status.Not_Approved },
		/* Deprecated */		{ }, // Status not allowed here
		/* Not_Approved */		{ Status.New_Proposed }
	};

	public SkosStatement(Resource subject, URI predicate, Value object) {
		super(subject, cleanSkosPredicate(subject,predicate), object);
	}

	public SkosStatement(Resource subject, URI predicate, Value object, long id) {
		super(subject, cleanSkosPredicate(subject,predicate), object);
		setId(id);
	}

	public long getId() {
		return id;
	}

	public void setId(long id) {
		this.id = id;
	}
	
	public String getRelationQualifier() {
		return relationQualifier;
	}

	public void setRelationQualifier(String relationQualifier) {
		this.relationQualifier = relationQualifier;
	}

	public String getTermSource() {
		return termSource;
	}

	public void setTermSource(String TermSource) {
		this.termSource = TermSource;
	}

	private static Predicate cleanSkosPredicate(Resource subject, URI predicate) {
		if (predicate != null) {
			if (predicate instanceof Predicate) {
				return (Predicate)predicate;
			} else /*if (predicate instanceof URI)*/ {
				String property = predicate.getLocalName();
				return Predicate.find(subject,property);
			}
		}
		return null;
	}

	public static Resource findSkosResource(SkosManager skosManager, Resource context, String about) {
		Resource aResource;
		boolean found = false;
		ConceptScheme aScheme = null;
		if (context != null) {
			if (context instanceof ConceptScheme) {
				aScheme = (ConceptScheme)context;
			} else if (context instanceof Concept) {
				aScheme = ((Concept)context).getInScheme();
			}
		}
		if (aScheme != null) {
			String prefixToRemove = aScheme.getPrefixToRemove();
			if (prefixToRemove != null
					&& prefixToRemove.length() > 0
					&& about.startsWith(prefixToRemove)) {
				found = true;
				about = about.substring(prefixToRemove.length());
			}

			prefixToRemove = aScheme.getNamespace();
			if (prefixToRemove != null
					&& prefixToRemove.length() > 0
					&& about.startsWith(prefixToRemove)) {
				found = true;
				about = about.substring(prefixToRemove.length());
			}

			String root = (aScheme.getAbout()+SchemeInterface.separator).toLowerCase();
			if (about.toLowerCase().startsWith(root)) {
				found = true;
				about = about.substring(root.length());
			}
			if (found) {
				aResource = aScheme.getConcept(about);
			}
		}

		if (about.startsWith(SchemeInterface.SESAME_NAMESPACE)) {
			aResource = (NoScheme) skosManager.getScheme(about.substring(SchemeInterface.SESAME_NAMESPACE.length()));
		} else if (about.startsWith(Concept.SESAME_NAMESPACE)) {
			aResource = skosManager.getScheme_Concept(about.substring(Concept.SESAME_NAMESPACE.length()));
			// } else if (about.startsWith(LinkedConcept.SESAME_NAMESPACE)) {  IDEM QUE Concept!!!
			//	aResource = skosManager.getScheme_Concept(about.substring(Concept.SESAME_NAMESPACE.length()));
		} else if (about.startsWith(Notation.SESAME_NAMESPACE)) {
			aResource = skosManager.getScheme_Concept(about.substring(Notation.SESAME_NAMESPACE.length()));
		} else if (about.startsWith(LocalLinkedConcept.SESAME_NAMESPACE)) {
			if (! (context instanceof Concept)) return null;
			aResource = ((Concept)context).getInScheme().getConcept(about.substring(LocalLinkedConcept.SESAME_NAMESPACE.length()));
		} else {
			if (! (context instanceof Concept)) return null;
			aResource = skosManager.getScheme_Concept(about);
		}
		return aResource;
	}

	public Concept getObjectConcept(SkosManager skosManager) {
		Resource aResource = findSkosResource(skosManager, this.getSubject(),this.getObject().toString());
		if (aResource instanceof Concept) return (Concept)aResource;
		else return null;
	}
	
	public NoScheme getObjectConceptScheme(SkosManager skosManager) {
		Resource aResource = findSkosResource(skosManager, this.getSubject(),this.getObject().toString());
		if (aResource instanceof SchemeInterface) return (NoScheme)aResource;
		else return null;
	}


	public static SkosStatement makeSkosStatement(Resource aSubject,
			String property, String relQualifier,
			TermFormat aFormat, String value,
			String lang) {
	
		SkosStatement statement = null;

		Value anObject = null;

		Predicate currProperty = Predicate.find(aSubject,property);
		if (currProperty != null) {
			Class objectClass = currProperty.getObjectClass();
			if (objectClass == LocalLinkedConcept.class) {
				if (value != null) {
					anObject = new URIImpl(LocalLinkedConcept.SESAME_NAMESPACE/*+((Concept)aSubject).getInScheme().getAbout_About(rel)*/+value);
				}
			} else if (objectClass == LinkedConcept.class) {
				if (value != null) {
					anObject = new URIImpl(LinkedConcept.SESAME_NAMESPACE+value);
				}
			} else if (objectClass == Term.class){
				if (value != null) {
					if (lang == null) lang = TermList.anyLanguage;
					anObject = new LiteralImpl(value,lang);
				}
			} else if (objectClass == Url.class){
				if (value != null) {
					if (lang == null) lang = TermList.anyLanguage;
					anObject = new LiteralImpl(value,lang);
				}
			} else if (objectClass == Notation.class) {
				if (value != null) {
					anObject = new URIImpl(Notation.SESAME_NAMESPACE+SkosUtil.normalizeAbout(value));
				}
			}
			statement = new SkosStatement (aSubject, currProperty, anObject);
			statement.setRelationQualifier(relQualifier);			
		}
		return statement;
	}

		public static SkosStatement makeSkosStatement(Resource aSubject,
			String property, String relQualifier,
			String literal, String literalLanguage,
			String notation, String rel) {
		SkosStatement statement = null;

		Value anObject = null;

		Predicate currProperty = Predicate.find(aSubject,property);
		if (currProperty != null) {
			Class objectClass = currProperty.getObjectClass();
			if (objectClass == LocalLinkedConcept.class) {
				if (rel != null) {
					anObject = new URIImpl(LocalLinkedConcept.SESAME_NAMESPACE/*+((Concept)aSubject).getInScheme().getAbout_About(rel)*/+rel);
				}
			} else if (objectClass == LinkedConcept.class) {
				if (rel != null) {
					anObject = new URIImpl(LinkedConcept.SESAME_NAMESPACE+rel);
				}
			} else if (objectClass == Term.class){
				if (literal != null) {
					if (literalLanguage == null) literalLanguage = TermList.anyLanguage;
					anObject = new LiteralImpl(literal,literalLanguage);
				}
			} else if (objectClass == Url.class){
				if (literal != null) {
					if (literalLanguage == null) literalLanguage = TermList.anyLanguage;
					anObject = new LiteralImpl(literal,literalLanguage);
				}
			} else if (objectClass == Notation.class) {
				if (notation != null) {
					anObject = new URIImpl(Notation.SESAME_NAMESPACE+SkosUtil.normalizeAbout(notation));
				} else {
					anObject = new URIImpl(Notation.SESAME_NAMESPACE+SkosUtil.normalizeAbout(literal));
				}
			}
			statement = new SkosStatement (aSubject, currProperty, anObject);
			statement.setRelationQualifier(relQualifier);
		}
		return statement;
	}

	public static SkosStatement makeSkosStatement(SkosManager skosManager,
			String about, String property, String relQualifier,
			TermFormat aFormat,
			String literal, String literalLanguage, String notation, String rel) {

		SkosStatement statement = null;

		Resource aSubject = findSkosResource(skosManager, null, about);
		if (aSubject != null) {
			statement = makeSkosStatement(aSubject,property,relQualifier,
										  literal, literalLanguage,notation,rel);
		}
		return statement;
	}

	public Value findExistingRelation() {
		Predicate predicate = (Predicate)this.getPredicate();
		Value theObject = this.getObject();
		if (predicate != null && theObject != null) {
			LinkedList<Value> content = null;
			try {
				content = (LinkedList<Value>) Predicate.getAll[predicate.ordinal()].invoke(this.getSubject());
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			if (content != null) {
				for (Value aResource: content) {
					if (aResource==null) continue;
					if (aResource.toString().equals(theObject.toString())) {
						return aResource;
					}
				}
			}
		}
		return null;
	}
	
	public Value prepareNewObject(Status aStatus) {
		Predicate predicate = (Predicate)this.getPredicate();
		if (predicate != null) {
			Class objectType = predicate.getObjectClass();
			Value theObject = this.getObject();
			if (objectType == Term.class) {
				if (theObject instanceof Term) return theObject;
				if (theObject instanceof Literal) {
					Term aTerm = new Term (((Literal)theObject).getLanguage(), TermFormat.REGULAR, ((Literal)theObject).getLabel(), termSource);
					aTerm.setCurrentStatus(aStatus);
					return aTerm;
				}
			} else if (objectType == Url.class) {
				if (theObject instanceof Url) return theObject;
				if (theObject instanceof Literal) {
					Url anUrl = new Url (((Literal)theObject).getLanguage(),((Literal)theObject).getLabel());
					anUrl.setCurrentStatus(aStatus);
					return anUrl;
				}
			} else if (objectType == LinkedConcept.class) {
				if (theObject instanceof LinkedConcept) return theObject;
				if (theObject instanceof LocalLinkedConcept) {
					LinkedConcept aLink = new LinkedConcept((Concept)this.getSubject(),(LocalLinkedConcept)theObject);
					aLink.setCurrentStatus(aStatus);
					return aLink;
				} else if (theObject instanceof URI) {
					LinkedConcept aLink = new LinkedConcept(((URI)theObject).getLocalName(),aStatus,relationQualifier);
					return aLink;
				}
 			} else if (objectType == LocalLinkedConcept.class) {
				if (theObject instanceof LocalLinkedConcept) return theObject;
				if (theObject instanceof URI) {
					LinkedConcept aLink = new LocalLinkedConcept(((URI)theObject).getLocalName(),aStatus,relationQualifier);
					return aLink;
				}
 			} else if (objectType == Notation.class) {
				if (theObject instanceof URI) {
					Notation aNotation = new Notation(((URI)theObject).getLocalName(),aStatus);
					return aNotation;
				}
 			}
		}
		return null;
	}

	public boolean addRelation (Value preparedObject) {
		Predicate predicate = (Predicate)this.getPredicate();
		if (predicate != null) {
			if (Predicate.readOnly[predicate.ordinal()]) return false;
			LinkedList<Value> content = null;
			try {
				content = (LinkedList<Value>) Predicate.getAll[predicate.ordinal()].invoke(this.getSubject());
				return content.add(preparedObject);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return false;
		} else return false;
	}
	
	public boolean removeRelation (Value existingObject) {
		Predicate predicate = (Predicate)this.getPredicate();
		if (predicate != null) {
			if (Predicate.readOnly[predicate.ordinal()]) return false;
			LinkedList<Value> content = null;
			try {
				content = (LinkedList<Value>) Predicate.getAll[predicate.ordinal()].invoke(this.getSubject());
				return content.remove(existingObject);
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			return false;
		} else return false;
	}

	public boolean putRelation () {
		return putRelation(this.getObject());
	}

	public boolean putRelation (Value preparedObject) {
		Predicate predicate = (Predicate)this.getPredicate();
		if (predicate != null) {
			if (Predicate.readOnly[predicate.ordinal()]) return false;
			Object content = null;
			try {
				content = (LinkedList<Value>) Predicate.putAll[predicate.ordinal()].invoke(this.getSubject(),preparedObject);
				if (content == null) return false;
				if (content.getClass() == Boolean.class) return (Boolean)content;
				return true;
			} catch (IllegalArgumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return false;
	}
	
}
